package com.github.ydoc.core;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.github.ydoc.config.YDocPropertiesConfig;
import com.github.ydoc.config.YapiApi;
import io.swagger.annotations.Api;
import lombok.extern.slf4j.Slf4j;
import org.springframework.aop.framework.AdvisedSupport;
import org.springframework.aop.support.AopUtils;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.EnvironmentAware;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.env.Environment;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import springfox.documentation.service.Documentation;
import springfox.documentation.spring.web.DocumentationCache;
import springfox.documentation.swagger2.mappers.ServiceModelToSwagger2Mapper;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.function.Supplier;

/**
 * author NoBugBoY description 获取全部文档 create 2021-04-22 14:24
 **/
@EnableConfigurationProperties(YDocPropertiesConfig.class)
@Slf4j
public class ScanControllerSwagger
        implements ApplicationContextAware, EnvironmentAware, InitializingBean, CommandLineRunner {
    private final YapiApi yapiApi;

    public ScanControllerSwagger(YapiApi yapiApi) {
        this.yapiApi = yapiApi;
    }

    @Autowired
    YDocPropertiesConfig propertiesConfig;
    @Autowired(required = false)
    DocumentationCache documentationCache;
    @Autowired(required = false)
    ServiceModelToSwagger2Mapper map;

    private ApplicationContext applicationContext;
    private Environment e;

    Supplier<String> basePath = () -> StringUtils.hasText(e.getProperty("server.servlet.context-path"))
            ? e.getProperty("server.servlet.context-path")
            : "/";

    public void scan() throws Exception {
        Map<String, Object> restControllerMap = applicationContext.getBeansWithAnnotation(RestController.class);
        Swagger swagger = Swagger.initialize();
        swagger.setDefinitions(Factory.definitions);
        swagger.setBasePath(basePath.get());
        List<Swagger.Tag> tags = new ArrayList<>();
        JSONObject paths = Factory.get();
        for (Map.Entry<String, Object> object : restControllerMap.entrySet()) {
            // 组装swagger-api
            Class<?> aClass = object.getValue().getClass();
            if (AopUtils.isCglibProxy(object.getValue())) {
                //如果启用了cglib的代理模式，需要获取原始类
                Field h = null;
                try {
                    h = object.getValue().getClass().getDeclaredField("CGLIB$CALLBACK_0");
                    h.setAccessible(true);
                    Object dynamicAdvisedInterceptor = h.get(object.getValue());
                    Field advised = dynamicAdvisedInterceptor.getClass().getDeclaredField("advised");
                    advised.setAccessible(true);
                    Object target = ((AdvisedSupport) advised.get(dynamicAdvisedInterceptor)).getTargetSource().getTarget();
                    if (target != null) {
                        aClass = target.getClass();
                    }
                } catch (NoSuchFieldException noSuchFieldException) {
                    noSuchFieldException.printStackTrace();
                } catch (IllegalAccessException illegalAccessException) {
                    illegalAccessException.printStackTrace();
                } catch (Exception exception) {
                    exception.printStackTrace();
                }
            }
            // 如果有外层路径需要加上
            String outPath = buildBaseUrl(aClass);
            if ("/swagger-json".equals(outPath) || outPath.contains("$")) {
                continue;
            }
            if (object.getKey().contains("swaggerApi") || object.getKey().contains("swagger2ControllerWebMvc")
                    || object.getKey().contains("apiResourceController")) {
                continue;
            }
            outPath = Factory.pathFormat.apply(outPath);
            // controller分组
            Api api = AnnotationUtils.findAnnotation(aClass, Api.class);
            String name = object.getKey();
            if (api != null) {
                if (api.hidden()) {
                    //设置了隐藏的controller，跳过接口输出
                    continue;
                }
                name = api.tags()[0];
            }
            tags.add(new Swagger.Tag(name, name));
            // 循环所有的restfulApi
            Method[] methods = aClass.getDeclaredMethods();

            for (Method method : methods) {
                RequestTypeMatchingSwagger.matching(paths, method, outPath, name);
            }
        }
        swagger.setPaths(paths);
        swagger.setTags(tags);
        Factory.json = JSON.toJSONString(swagger);

        if (propertiesConfig.isPrint()) {
            print();
        }
        if (enableImport()) {
            importToYApi();
        }

    }

    public void print() {
        log.info(Factory.json);
    }

    public boolean enableImport() {
        return StringUtils.hasText(propertiesConfig.getHost()) && StringUtils.hasText(propertiesConfig.getToken());
    }

    public synchronized void importToYApi() throws Exception {
        yapiApi.importDoc(propertiesConfig.isCloud(), propertiesConfig.getToken(), propertiesConfig.getHost(),
                Factory.json);
        Factory.definitions.clear();
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }

    public String buildBaseUrl(Class<?> aClass) {
        String basePath = "";
        RequestMapping requestMapping = AnnotationUtils.findAnnotation(aClass, RequestMapping.class);
        if (requestMapping != null) {
            if (requestMapping.value().length > 0) {
                basePath = requestMapping.value()[0];
            } else if (requestMapping.path().length > 0) {
                basePath = requestMapping.path()[0];
            }
        }
        return basePath;
    }

    @Override
    public void setEnvironment(Environment environment) {
        this.e = environment;
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        if (propertiesConfig.isEnable()) {
            if (!propertiesConfig.isSwaggerNative()) {
                new Thread(() -> {
                    log.info(" >>> YDoc Sync Api start !<<<");
                    try {
                        scan();
                        log.info(" >>> YDoc Sync Api End !<<<");
                    } catch (Exception e) {
                        log.info(" >>> YDoc Sync Api fail !<<<");
                    }
                }).start();
            }
        }
    }

    @Override
    public void run(String... args) throws Exception {
        if (propertiesConfig.isSwaggerNative() && documentationCache != null && map != null) {
            // 这里暂时只考虑一个group的时候，因为大多数情况都是只有一个default
            log.info(" >>> YDoc Sync Api start !<<<");
            if (documentationCache.all().values().size() > 0) {
                Documentation documentation = new ArrayList<>(documentationCache.all().values()).get(0);
                io.swagger.models.Swagger swagger = this.map.mapDocumentation(documentation);
                Factory.json = JSON.toJSONString(swagger);
                if (propertiesConfig.isPrint()) {
                    print();
                }
                if (enableImport()) {
                    importToYApi();
                }
            } else {
                log.warn("未发现任何Api,可能未配置Swagger2 Config....");
            }
            log.info(" >>> YDoc Sync Api End !<<<");
        }
        // access调用链
        if (access()) {
            YapiAccess login = yapiApi.login();
            // 异步执行即可
            if (propertiesConfig.isAutoTest()) {
                yapiApi.autoTest(login);
            }
        }
        // no access
    }

    /**
     * 某些方法必须login才ok
     */
    private boolean access() {
        return StringUtils.hasText(propertiesConfig.getYapiUserEmail())
                && StringUtils.hasText(propertiesConfig.getYapiUserPassword());
    }
}
